/*------------------------------------------------------------------------------
--------------------------------------------------------------------------------
Copyright (c) 2016, Loongson Technology Corporation Limited.

All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this 
list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice, 
this list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.

3. Neither the name of Loongson Technology Corporation Limited nor the names of 
its contributors may be used to endorse or promote products derived from this 
software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
DISCLAIMED. IN NO EVENT SHALL LOONGSON TECHNOLOGY CORPORATION LIMITED BE LIABLE
TO ANY PARTY FOR DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
------------------------------------------------------------------------------*/
`timescale 1ns / 1ps
`include "config.h"
// `define FFT_OUTPUT_TXT

`define UART_PSEL               u_soc_top.u_apb_subsys.uart0.PSEL
`define UART_PENBLE             u_soc_top.u_apb_subsys.uart0.PENABLE
`define UART_PWRITE             u_soc_top.u_apb_subsys.uart0.PWRITE
`define UART_WADDR              u_soc_top.u_apb_subsys.uart0.PADDR[7:0]
`define UART_WDATA              u_soc_top.u_apb_subsys.uart0.PWDATA[7:0]
`define MIG_AXI                 u_soc_top.ddr3.u_axi_wrap_ddr.u_sdram

module tb_top( );
reg reset_n;
reg clk;
wire clk_o;

wire         UART_RX;
wire         UART_TX;
wire [31:0]  GPIO;

wire SPI_SCK = GPIO[0];
wire SPI_CSN = GPIO[1];
wire SPI_MOSI = GPIO[2];
wire SPI_MISO = GPIO[3];
wire I2C_SCL = GPIO[6];
wire I2C_SDA = GPIO[7];

pullup(GPIO[6]);
pullup(GPIO[7]);

//io
wire [15:0] led;
wire [1 :0] led_rg0;
wire [1 :0] led_rg1;
wire [7 :0] num_csn;
wire [6 :0] num_a_g;
wire [7 :0] switch;
wire [3 :0] btn_key_col;
reg  [3 :0] btn_key_row;
wire [1 :0] btn_step;


assign switch      = 8'hff;
assign btn_step    = 2'd3;

//ddr3
wire  [12:0]  ddr3_addr;
wire  [2 :0]  ddr3_ba;
wire  ddr3_ras_n;
wire  ddr3_cas_n;
wire  ddr3_we_n;
wire  ddr3_odt;
wire  ddr3_reset_n;
wire  ddr3_cke;
wire  [1:0]  ddr3_dm;
wire  ddr3_ck_p;
wire  ddr3_ck_n;
wire  [15:0]  ddr3_dq;
wire  [1:0]  ddr3_dqs_p;
wire  [1:0]  ddr3_dqs_n;

// sdram
wire [12:0]     dram_addr;
wire [1:0]      dram_ba;
wire            dram_cas_n;
wire            dram_cke;
wire            dram_clk;
wire [1:0]      dram_cs_n;
wire [31:0]     dram_dq;
wire [1:0]      dram_dqml;
wire            dram_ras_n;
wire [1:0]      dram_dqmh;
wire            dram_we_n;

initial
begin
    clk = 1'b0;
    reset_n = 1'b0;
    #2000;
    reset_n = 1'b1;
end
always #5 clk=~clk;

initial begin
    btn_key_row = 4'b1111;
    #3000000;

    btn_key_row = 4'b1110;
    wait(btn_key_col == 4'b1110);
    #20;
    btn_key_row = 4'b1111;

    #100000
    btn_key_row = 4'b1110;
    wait(btn_key_col == 4'b1110);
    #1;
    btn_key_row = 4'b1111;
    wait(btn_key_col == 4'b1101);
    #1;
    btn_key_row = 4'b1110;
    #20;
    btn_key_row = 4'b1111;

    #100000
    btn_key_row = 4'b1110;
    wait(btn_key_col == 4'b1110);
    #1;
    btn_key_row = 4'b1111;
    wait(btn_key_col == 4'b1011);
    #1;
    btn_key_row = 4'b1110;
    #20;
    btn_key_row = 4'b1111;

    #100000
    btn_key_row = 4'b1110;
    wait(btn_key_col == 4'b1110);
    #1;
    btn_key_row = 4'b1111;
    wait(btn_key_col == 4'b0111);
    #1;
    btn_key_row = 4'b1110;
    #20;
    btn_key_row = 4'b1111;

end

soc_top  #(.SIMULATION(1'b1)) u_soc_top (
    .clk                     ( clk            ),
    .reset_n                 ( reset_n        ),
    .clk_o                   ( clk_o          ),

    //------gpio-------
    .num_csn                 (num_csn         ),
    .num_a_g                 (num_a_g         ),
    .led                     (led             ),
    .led_rg0                 (led_rg0         ),
    .led_rg1                 (led_rg1         ),
    .switch                  (switch          ),
    .btn_key_col             (btn_key_col     ),
    .btn_key_row             (btn_key_row     ),
    .btn_step                (btn_step        ),

     .dram_clk                ( dram_clk          ),
     .dram_addr               ( dram_addr         ),
     .dram_ba                 ( dram_ba           ),
     .dram_ras_n              ( dram_ras_n        ),
     .dram_cas_n              ( dram_cas_n        ),
     .dram_we_n               ( dram_we_n         ),
     .dram_cke                ( dram_cke          ),
     .dram_cs_n               ( dram_cs_n         ),
     .dram_dq                 ( dram_dq           ),
     .dram_dqml               ( dram_dqml         ),
     .dram_dqmh               ( dram_dqmh         ),
    //------ddr3-------
//    .ddr3_addr               ( ddr3_addr      ),
//    .ddr3_ba                 ( ddr3_ba        ),
//    .ddr3_ras_n              ( ddr3_ras_n     ),
//    .ddr3_cas_n              ( ddr3_cas_n     ),
//    .ddr3_we_n               ( ddr3_we_n      ),
//    .ddr3_odt                ( ddr3_odt       ),
//    .ddr3_reset_n            ( ddr3_reset_n   ),
//    .ddr3_cke                ( ddr3_cke       ),
//    .ddr3_dm                 ( ddr3_dm        ),
//    .ddr3_ck_p               ( ddr3_ck_p      ),
//    .ddr3_ck_n               ( ddr3_ck_n      ),
//    .ddr3_dq                 ( ddr3_dq        ),
//    .ddr3_dqs_p              ( ddr3_dqs_p     ),
//    .ddr3_dqs_n              ( ddr3_dqs_n     ),

    .UART_RX                 ( UART_RX       ),
    .UART_TX                 ( UART_TX       ),

    .GPIO                    ( GPIO          )
);

reg   i_TX_DV;
reg   [7:0]  i_TX_Byte;
wire  o_RX_DV;
wire  [7:0]  o_RX_Byte;
initial begin
    i_TX_DV = 1'b0;
    i_TX_Byte = 8'h00;
    #2000;

    wait(o_RX_DV && (o_RX_Byte == 8'h9f));
    #1;
    i_TX_DV = 1'b1;
    i_TX_Byte = 8'h1c;
    @(posedge clk);
    #1;
    i_TX_DV = 1'b0;
    i_TX_Byte = 8'h00;

    wait(~o_RX_DV);
    #1;
    wait(o_RX_DV && (o_RX_Byte == 8'h00));
    #1;
    i_TX_DV = 1'b1;
    i_TX_Byte = 8'h31;
    @(posedge clk);
    #1;
    i_TX_DV = 1'b0;
    i_TX_Byte = 8'h00;

    wait(~o_RX_DV);
    #1;
    wait(o_RX_DV && (o_RX_Byte == 8'h00));
    #1;
    i_TX_DV = 1'b1;
    i_TX_Byte = 8'h14;
    @(posedge clk);
    #1;
    i_TX_DV = 1'b0;
    i_TX_Byte = 8'h00;
end

SPI_Slave  u_SPI_Slave (
    .i_Rst_L                 ( reset_n      ),
    .i_Clk                   ( clk          ),
    .i_TX_DV                 ( i_TX_DV      ),
    .i_TX_Byte               ( i_TX_Byte    ),
    .o_RX_DV                 ( o_RX_DV      ),
    .o_RX_Byte               ( o_RX_Byte    ),
    .i_SPI_Clk               ( GPIO[0]      ),
    .i_SPI_CS_n              ( GPIO[1]      ),
    .i_SPI_MOSI              ( GPIO[2]      ),
    .o_SPI_MISO              ( GPIO[3]      )
);

EEPROM_AT24C64 u_EEPROM_AT24C64 (
    .scl                     ( GPIO[6]    ),
    .sda                     ( GPIO[7]    )
);

//模拟串口打印
wire uart_display;
wire [7:0] uart_data;
wire uart_wen;
assign uart_wen = (`UART_PSEL == 1'b1) &&  (`UART_PENBLE == 1'b1) && (`UART_PWRITE == 1'b1);
assign uart_display = (uart_wen == 1'b1) && (`UART_WADDR == 8'h0);
assign uart_data    = `UART_WDATA;

always @(posedge clk)
begin
    if(uart_display)
    begin
        if(uart_data==8'hff)
        begin
            ;//$finish;
        end
        else
        begin
            $write("%c",uart_data);
        end
    end
end

generate if(`SIMU_USE_DDR==0) begin: sim_ram_tb
/*
    integer software_bin;
    integer err,str;
    reg [31:0] instr;
    integer i;
    initial begin
        software_bin = $fopen("../../../../../inst_data.bin","rb");
        err = $ferror(software_bin, str);
        if(!err) begin
            for(i=0;i<262144;i=i+1) begin
                if ($fread(instr,software_bin))begin
                    u_soc_top.sim_ram.u_axi_ram.u_fpga_sram.BRAM[i] <= {instr[7:0],instr[15:8],instr[23:16],instr[31:24]};
                end
                else begin
                    u_soc_top.sim_ram.u_axi_ram.u_fpga_sram.BRAM[i] <= 32'b0;
                end
            end
        end
        $fclose(software_bin);
    end
*/
end
else begin: ddr3_tb

    // AXI4写数据任�???
    task axi4_write;
        input [31:0] addr;
        input [31:0] data;
        begin
            // 写地�?通道
            @(posedge `MIG_AXI.clk);
            force `MIG_AXI.s_axi_awid = 4'b0001;
            force `MIG_AXI.s_axi_awaddr = addr[27:0];
            force `MIG_AXI.s_axi_awlen   = 8'h00;
            force `MIG_AXI.s_axi_awsize  = 3'b010;
            force `MIG_AXI.s_axi_awburst = 2'b01;
            force `MIG_AXI.s_axi_awlock  = 1'b0;
            force `MIG_AXI.s_axi_awcache = 4'b0000;
            force `MIG_AXI.s_axi_awprot  = 3'b000;
//            force `MIG_AXI.s_axi_awqos   = 4'b0000;
            force `MIG_AXI.s_axi_awvalid = 1'b1;
            
            // 等待握手完成
            wait(`MIG_AXI.s_axi_awready);
            @(posedge `MIG_AXI.clk);
            #1;
            force `MIG_AXI.s_axi_awvalid = 1'b0;
            
            // 写数据�?�道
            force `MIG_AXI.s_axi_wdata  = data;
            force `MIG_AXI.s_axi_wstrb  = 4'b1111;
            force `MIG_AXI.s_axi_wlast  = 1'b1;
            force `MIG_AXI.s_axi_wvalid = 1'b1;
            
            // 等待写数据握手完�??
            wait(`MIG_AXI.s_axi_wready);
            @(posedge `MIG_AXI.clk);
            #1;
            force `MIG_AXI.s_axi_wvalid = 1'b0;
            
            // 等待写响�??
            force `MIG_AXI.s_axi_bready = 1'b1;
            wait(`MIG_AXI.s_axi_bvalid);
            @(posedge `MIG_AXI.clk);
            #1;
            force `MIG_AXI.s_axi_bready = 1'b0;
        end
    endtask

    // 从文件写入AXI总线的任�??
    task write_file;
        input [31:0] base_addr;  // 基地�?
        input string filename;    // 文件�?
        reg [31:0] data;         // 临时数据存储
        integer fd;              // 文件描述�?
        integer bytes_read;      // 读取的字节数
        integer addr_offset;     // 地址偏移
        begin
            // 打开二进制文�?
            fd = $fopen(filename, "rb");
            if (fd == 0) begin
                $display("Error: Unable to open file %s", filename);
                return;
            end
            
            addr_offset = 0;
            
            // 循环读取文件内容并写入AXI总线
            while (!$feof(fd)) begin
                bytes_read = $fread(data, fd);
                if (bytes_read > 0) begin
                    // 调用AXI写任�?
                    axi4_write(
                        base_addr + addr_offset,  // 目标地址
                        {data[7:0],data[15:8],data[23:16],data[31:24]} // 写入数据
                    );
                    addr_offset = addr_offset + 4;
                end
            end
            
            // 关闭文件
            $fclose(fd);
            release `MIG_AXI.s_axi_awid;
            release `MIG_AXI.s_axi_awaddr;
            release `MIG_AXI.s_axi_awlen;
            release `MIG_AXI.s_axi_awsize;
            release `MIG_AXI.s_axi_awburst;
            release `MIG_AXI.s_axi_awlock;
            release `MIG_AXI.s_axi_awcache;
            release `MIG_AXI.s_axi_awprot;
//            release `MIG_AXI.s_axi_awqos;
            release `MIG_AXI.s_axi_awvalid;
            release `MIG_AXI.s_axi_wdata ;
            release `MIG_AXI.s_axi_wstrb ;
            release `MIG_AXI.s_axi_wlast ;
            release `MIG_AXI.s_axi_wvalid;
            release `MIG_AXI.s_axi_bready;
            $display("File %s is written, and a total of %0d bytes are written", filename, addr_offset);
        end
    endtask

    initial begin
        force u_soc_top.ddr_data_init = 1'b0;
//        wait(`MIG_AXI.init_calib_complete);
        wait(`MIG_AXI.init_end);
        write_file(32'h1c000000,"../../../../../../sdk/hello_world.bin");
//        @(posedge `MIG_AXI.ui_clk);
//        force u_soc_top.ddr3.u_axi_wrap_ddr.u_Axi_CDC_mig.axiOutRstn = 1'b0;
//        @(posedge `MIG_AXI.ui_clk);
//        force u_soc_top.ddr3.u_axi_wrap_ddr.u_Axi_CDC_mig.axiOutRstn = 1'b1;
        force u_soc_top.ddr_data_init = 1'b1;
    end

     sdram_model u_sdram_model (
         .clk(dram_clk),
         .rst_n(reset_n),
         .addr(dram_addr),
         .ba(dram_ba),
         .cas_n(dram_cas_n),
         .cke(dram_cke),
         .cs_n(dram_cs_n),
         .dq(dram_dq),
         .dqmh(dram_dqmh),
         .ras_n(dram_ras_n),
         .dqml(dram_dqml),
         .we_n(dram_we_n)
     );

//    ddr3_model	u_ddr3_model (	
//        .rst_n   		(reset_n),	
//        .ck      		(ddr3_ck_p),	
//        .ck_n    		(ddr3_ck_n),	
//        .cke     		(ddr3_cke),	
//        .cs_n    		(1'b0),	
//        .ras_n   		(ddr3_ras_n),	
//        .cas_n   		(ddr3_cas_n),	
//        .we_n    		(ddr3_we_n),	
//        .dm_tdqs 		(ddr3_dm),	
//        .ba      		(ddr3_ba),	
//        .addr    		(ddr3_addr),	
//        .dq      		(ddr3_dq),	
//        .dqs     		(ddr3_dqs_p),	
//        .dqs_n   		(ddr3_dqs_n),	
//        .tdqs_n  		(),
//        .odt     		(ddr3_odt)	
//    );

end
endgenerate

//FFT测试结果输出
`ifdef FFT_OUTPUT_TXT
    integer fft_output_re;
    integer fft_output_im;
    initial begin
        fft_output_re = $fopen("../../../../../../python/fft512_output_re.txt", "w"); 
        fft_output_im = $fopen("../../../../../../python/fft512_output_im.txt", "w");
        forever begin
        @(posedge u_soc_top.u_axi_fft_top.u_axi_fft_wrap.aclk);
        if(u_soc_top.u_axi_fft_top.u_axi_fft_wrap.valid_out) begin
            $fwrite(fft_output_re, "%04h\n", u_soc_top.u_axi_fft_top.u_axi_fft_wrap.y_re);
            $fwrite(fft_output_im, "%04h\n", u_soc_top.u_axi_fft_top.u_axi_fft_wrap.y_im);
        end
        end
    end
`endif

endmodule
